#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <arpa/nameser.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include "handle_http_connection.h"
#include "http_server.h"
#include "write_buffer.h"
#include "send_error_page.h"
#include "end_of_request.h"
#include "dirlisting.h"
#include "accesslist.h"
#include "parsedate.h"
#include "parse_uri.h"
#include "read_buffer.h"
#include "dnscache.h"
#include "url.h"
#include "log.h"
#include "mime.h"
#include "rulelist.h"
#include "get_file_extension.h"
#include "bounded_string.h"
#include "version.h"

#define FILE_RESPONSE                     0
#define REVERSE_PROXY_CONNECTION_RESPONSE 1
#define PROXY_CONNECTION_RESPONSE         2
#define FCGI_CONNECTION_RESPONSE          3
#define ERROR_CODES                       400

#define REQUEST_ENTITY_TOO_LARGE          (20 * 1024)

#define BOUNDARY_SIZE                     20

#define INDEX_PAGE                        "index.html"

#define ARRAY_SIZE(x)                     (sizeof (x) / sizeof (*x))

#define IS_WHITE_SPACE(x)                 ((x == ' ') || (x == '\t'))
#define IS_ALPHA(x)                       (((x >= 'A') && (x <= 'Z')) || ((x >= 'a') && (x <= 'z')))
#define IS_DIGIT(x)                       ((x >= '0') && (x <= '9'))

static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
static char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

BOUNDED_STRING methods[15] = {
		{"CONNECT", 7},
		{"COPY", 4},
		{"DELETE", 6},
		{"GET", 3},
		{"HEAD", 4},
		{"LOCK", 4},
		{"MKCOL", 5},
		{"MOVE", 4},
		{"OPTIONS", 7},
		{"POST", 4},
		{"PROPFIND", 8},
		{"PROPPATCH", 9},
		{"PUT", 3},
		{"TRACE", 5},
		{"UNLOCK", 6}
};

extern configuration_t mime_types;

static int read_request (http_connection_t *http_connection);
static int process_request (http_connection_t *http_connection, host_t **host, const char **mime_type);
static int prepare_file_response (http_connection_t *http_connection, host_t *host, const char *mime_type);
static void generate_boundary (char *boundary, size_t len);
static int do_reverse_proxy_connection (http_connection_t *http_connection, host_t *host);
static int do_proxy_connection (http_connection_t *http_connection);
static int do_fcgi_connection (http_connection_t *http_connection, host_t *host);
static int connect_to_http_server (dnscache_entry_t *dnscache_entry, unsigned short port);
static int connect_to_ip (unsigned int ip, unsigned short port);

int handle_http_connection (http_connection_t *http_connection, int events)
{
	/* Return values:
	 * -1: The connection will be closed.
	 *  0: The connection will be kept open.
	 */

	rangelist_t *rangelist;
	host_t *host;
	const char *mime_type;
	size_t nranges;
	off_t to;
	int ret;

	if ((events & EVENT_ERR) || (events & EVENT_HUP)) {
		return -1;
	}

	host = NULL;

	if (http_connection->http_connection_state == READING_REQUEST) {
		if ((ret = read_request (http_connection)) <= 0) {
			return ret;
		}

		if (ret >= ERROR_CODES) {
			if (http_server.fn_prepare_for_writing (http_connection) < 0) {
				return -1;
			}

			return send_error_page (http_connection, ret, host);
		}

		if ((ret = process_request (http_connection, &host, &mime_type)) < 0) {
			return -1;
		} else if (ret >= ERROR_CODES) {
			if (http_server.fn_prepare_for_writing (http_connection) < 0) {
				return -1;
			}

			return send_error_page (http_connection, ret, host);
		} else if (ret == FILE_RESPONSE) {
			if ((ret = prepare_file_response (http_connection, host, mime_type)) < 0) {
				return -1;
			}

			if (http_server.fn_prepare_for_writing (http_connection) < 0) {
				return -1;
			}

			if (ret != 0) {
				return send_error_page (http_connection, ret, host);
			}
		} else if (ret == REVERSE_PROXY_CONNECTION_RESPONSE) {
			if ((ret = do_reverse_proxy_connection (http_connection, host)) <= 0) {
				return ret;
			}

			if (http_server.fn_prepare_for_writing (http_connection) < 0) {
				return -1;
			}

			return send_error_page (http_connection, ret, host);
		} else if (ret == PROXY_CONNECTION_RESPONSE) {
			if ((ret = do_proxy_connection (http_connection)) <= 0) {
				return ret;
			}

			if (http_server.fn_prepare_for_writing (http_connection) < 0) {
				return -1;
			}

			return send_error_page (http_connection, ret, host);
		} else if (ret == FCGI_CONNECTION_RESPONSE) {
			if ((ret = do_fcgi_connection (http_connection, host)) <= 0) {
				return ret;
			}

			if (http_server.fn_prepare_for_writing (http_connection) < 0) {
				return -1;
			}

			return send_error_page (http_connection, ret, host);
		}
	}

	if (http_connection->http_connection_state == WRITING_MESSAGE_HEADERS) {
		if ((ret = write_buffer (http_connection, &http_connection->buffer)) <= 0) {
			return ret;
		}

		if (http_connection->http_method == HEAD_METHOD) {
			return end_of_request (http_connection);
		}

		http_connection->offset = 0;

		/* Directory listing? */
		if (http_connection->response_type == DIRECTORY_LISTING_RESPONSE) {
			http_connection->http_connection_state = WRITING_DIRECTORY_LISTING;
		} else {
			/* A file must be sent. */
			http_connection->http_connection_state = WRITING_RANGE;
		}
	}

	if (http_connection->http_connection_state == WRITING_RANGE) {
		rangelist = &http_connection->rangelist;
		nranges = rangelist->used;

		if (nranges == 0) {
			to = http_connection->filesize - 1;
		} else {
			http_connection->offset = rangelist->ranges[http_connection->current_range].from;
			to = rangelist->ranges[http_connection->current_range].to;
		}

		if ((ret = http_server.fn_write_file_range (http_connection, to)) <= 0) {
			return ret;
		}

		if (nranges <= 1) {
			return end_of_request (http_connection);
		}

		http_connection->current_range++;
		for (; http_connection->current_range < nranges; http_connection->current_range++) {
			http_connection->http_connection_state = WRITING_BOUNDARY;
			http_connection->offset = 0;

			if ((ret = write_buffer (http_connection, &(http_connection->parts[http_connection->current_range]))) <= 0) {
				return ret;
			}

			http_connection->http_connection_state = WRITING_RANGE;
			http_connection->offset = rangelist->ranges[http_connection->current_range].from;

			if ((ret = http_server.fn_write_file_range (http_connection, rangelist->ranges[http_connection->current_range].to)) <= 0) {
				return ret;
			}
		}

		http_connection->http_connection_state = WRITING_BOUNDARY;
		http_connection->offset = 0;

		if ((ret = write_buffer (http_connection, &(http_connection->parts[nranges]))) <= 0) {
			return ret;
		}

		return end_of_request (http_connection);
	}

	if (http_connection->http_connection_state == WRITING_BOUNDARY) {
		rangelist = &http_connection->rangelist;
		nranges = rangelist->used;

		for (; http_connection->current_range < nranges; http_connection->current_range++) {
			if ((ret = write_buffer (http_connection, &(http_connection->parts[http_connection->current_range]))) <= 0) {
				return ret;
			}

			http_connection->http_connection_state = WRITING_RANGE;
			http_connection->offset = rangelist->ranges[http_connection->current_range].from;

			if ((ret = http_server.fn_write_file_range (http_connection, rangelist->ranges[http_connection->current_range].to)) <= 0) {
				return ret;
			}

			http_connection->http_connection_state = WRITING_BOUNDARY;
			http_connection->offset = 0;
		}

		if ((ret = write_buffer (http_connection, &(http_connection->parts[nranges]))) <= 0) {
			return ret;
		}

		return end_of_request (http_connection);
	}

	if ((http_connection->http_connection_state == WRITING_ERROR_PAGE) || (http_connection->http_connection_state == WRITING_DIRECTORY_LISTING)) {
		if ((ret = write_buffer (http_connection, &http_connection->message_body)) <= 0) {
			return ret;
		}

		return end_of_request (http_connection);
	}

	return -1;
}

int read_request (http_connection_t *http_connection)
{
	buffer_t *buffer = &http_connection->buffer;
	char *data;
	char *ptr;
	char *end;
	off_t offset;
	unsigned char c;
	size_t len;
	size_t hostlen;
	int port;
	unsigned char *path;
	size_t pathlen;
	size_t content_length;
	int ret;

	do {
		if (buffer->used >= REQUEST_ENTITY_TOO_LARGE) {
			return 413; /* Request Entity Too Large. */
		}

		if (buffer_allocate (buffer, buffer->buffer_increment) < 0) {
			return -1;
		}

		if ((ret = read_buffer (http_connection)) <= 0) {
			return ret;
		}

		data = buffer->data;
		offset = http_connection->offset;

		while (offset < buffer->used) {
			c = (unsigned char) data[offset];
			switch (http_connection->request_state) {
				case 0: /* Initial state. */
					if (IS_ALPHA (c)) {
						http_connection->token = offset;

						http_connection->request_state = 1; /* Method. */
					} else if (c == '\r') {
						http_connection->request_state = 14; /* '\r' */
					} else if (c != '\n') {
						return 400; /* Bad Request. */
					}

					break;
				case 1: /* Method. */
					if (IS_ALPHA (c)) {
						if (offset - http_connection->token >= 7) {
							return 400; /* Bad Request. */
						}
					} else if (IS_WHITE_SPACE (c)) {
						http_connection->http_method = (eHttpMethod) search_string (methods, ARRAY_SIZE (methods), strncmp, data + http_connection->token, offset - http_connection->token);
						if (http_connection->http_method == NO_METHOD) {
							return 400; /* Bad Request. */
						}

						http_connection->request_state = 2; /* Whitespace between method and URI. */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 2: /* Whitespace between method and URI. */
					if (c > ' ') {
						http_connection->token = offset;

						http_connection->request_state = 3; /* URI. */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 3: /* URI. */
					if ((IS_WHITE_SPACE (c)) || (c == '\r') || (c == '\n')) {
						len = offset - http_connection->token;
						if (len == 1) {
							if (*(data + http_connection->token) != '/') {
								if ((*(data + http_connection->token) != '*') || (http_connection->http_method != OPTIONS_METHOD)) {
									return 400; /* Bad Request. */
								}
							}

							path = (unsigned char *) data + http_connection->token;
						} else if (len > sizeof ("http://") - 1) {
							if (strncasecmp (data + http_connection->token, "http://", 7) == 0) {
								if (parse_uri ((const unsigned char *) data + http_connection->token + 7, len - 7, &hostlen, &http_connection->port, (const unsigned char **) &path, &pathlen, &http_connection->ip) < 0) {
									return 400; /* Bad Request. */
								}

								if (buffer_append_nul_terminated_string (&http_connection->host, data + http_connection->token + 7, hostlen) < 0) {
									return -1;
								}

								len = pathlen;

								http_connection->proxy_connection = 1;
							} else if (*(data + http_connection->token) != '/') {
								if (http_connection->http_method != CONNECT_METHOD) {
									return 400; /* Bad Request. */
								}

								path = (unsigned char *) data + http_connection->token;
							} else {
								path = (unsigned char *) data + http_connection->token;
							}
						} else {
							if (*(data + http_connection->token) != '/') {
								if (http_connection->http_method != CONNECT_METHOD) {
									return 400; /* Bad Request. */
								}
							}

							path = (unsigned char *) data + http_connection->token;
						}

						if (buffer_append_nul_terminated_string (&http_connection->path, (const char *) path, len) < 0) {
							return -1;
						}

						if (IS_WHITE_SPACE (c)) {
							http_connection->request_state = 4; /* Whitespace after URI. */
						} else if (c == '\r') {
							http_connection->request_state = 14; /* '\r' */
						} else {
							http_connection->nCRLFs++;
							http_connection->request_state = 15; /* '\n' */
						}
					} else if (c < ' ') {
						return 400; /* Bad Request. */
					}

					break;
				case 4: /* Whitespace after URI. */
					if ((c == 'H') || (c == 'h')) {
						http_connection->request_state = 5; /* [H]TTP/<major>.<minor> */
					} else if (c == '\r') {
						http_connection->request_state = 14; /* '\r' */
					} else if (c == '\n') {
						http_connection->nCRLFs++;
						http_connection->request_state = 15; /* '\n' */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 5: /* [H]TTP/<major>.<minor> */
					if ((c == 'T') || (c == 't')) {
						http_connection->request_state = 6; /* H[T]TP/<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 6: /* H[T]TP/<major>.<minor> */
					if ((c == 'T') || (c == 't')) {
						http_connection->request_state = 7; /* HT[T]P/<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 7: /* HT[T]P/<major>.<minor> */
					if ((c == 'P') || (c == 'p')) {
						http_connection->request_state = 8; /* HTT[P]/<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 8: /* HTT[P]/<major>.<minor> */
					if (c == '/') {
						http_connection->request_state = 9; /* HTTP[/]<major>.<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 9: /* HTTP[/]<major>.<minor> */
					if (IS_DIGIT (c)) {
						http_connection->major_number = c - '0';

						http_connection->request_state = 10; /* HTTP/[<major>].<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 10: /* HTTP/[<major>].<minor> */
					if (IS_DIGIT (c)) {
						http_connection->major_number = (10 * http_connection->major_number) + (c - '0');
					} else if (c == '.') {
						http_connection->request_state = 11; /* HTTP/<major>[.]<minor> */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 11: /* HTTP/<major>[.]<minor> */
					if (IS_DIGIT (c)) {
						http_connection->minor_number = c - '0';

						http_connection->request_state = 12; /* HTTP/<major>.[<minor>] */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 12: /* HTTP/<major>.[<minor>] */
					if (IS_DIGIT (c)) {
						http_connection->minor_number = (10 * http_connection->minor_number) + (c - '0');
					} else if (IS_WHITE_SPACE (c)) {
						http_connection->keep_alive = (http_connection->major_number == 1) && (http_connection->minor_number == 1);

						http_connection->request_state = 13; /* Whitespace after version. */
					} else if (c == '\r') {
						http_connection->keep_alive = (http_connection->major_number == 1) && (http_connection->minor_number == 1);

						http_connection->request_state = 14; /* '\r' */
					} else if (c == '\n') {
						http_connection->keep_alive = (http_connection->major_number == 1) && (http_connection->minor_number == 1);

						http_connection->nCRLFs++;
						http_connection->request_state = 15; /* '\n' */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 13: /* Whitespace after version. */
					if (c == '\r') {
						http_connection->request_state = 14; /* '\r' */
					} else if (c == '\n') {
						http_connection->nCRLFs++;
						http_connection->request_state = 15; /* '\n' */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 14: /* '\r' */
					if (c == '\n') {
						if (http_connection->http_method == NO_METHOD) {
							http_connection->request_state = 0; /* Initial state. */
						} else {
							if (http_connection->nCRLFs == 1) {
								if (((http_connection->http_method == POST_METHOD) || (http_connection->http_method == PUT_METHOD)) && (http_connection->content_length == -1) && (!http_connection->chunked)) {
									return 400; /* Bad Request. */
								}

								http_connection->header_offsets[0][1] = offset + 1;
								return 200;
							}

							http_connection->nCRLFs++;

							http_connection->request_state = 15; /* '\n' */
						}
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 15: /* '\n' */
					if (http_connection->noffsets == 0) {
						http_connection->header_offsets[0][0] = offset;
						http_connection->noffsets++;
					}

					if (c == '\r') {
						if ((http_connection->message_header == CONNECTION_HEADER) || (http_connection->message_header == HOST_HEADER) || (http_connection->message_header == KEEP_ALIVE_HEADER) || (http_connection->message_header == PROXY_CONNECTION_HEADER)) {
							http_connection->header_offsets[http_connection->noffsets][1] = offset;
							http_connection->noffsets++;
						}

						http_connection->request_state = 14; /* '\r' */
					} else if (c == '\n') {
						if (((http_connection->http_method == POST_METHOD) || (http_connection->http_method == PUT_METHOD)) && (http_connection->content_length == -1) && (!http_connection->chunked)) {
							return 400; /* Bad Request. */
						}

						if ((http_connection->message_header == CONNECTION_HEADER) || (http_connection->message_header == HOST_HEADER) || (http_connection->message_header == KEEP_ALIVE_HEADER) || (http_connection->message_header == PROXY_CONNECTION_HEADER)) {
							http_connection->header_offsets[http_connection->noffsets][1] = offset;
							http_connection->noffsets++;
						}

						http_connection->header_offsets[0][1] = offset + 1;
						return 200;
					} else if (IS_WHITE_SPACE (c)) {
						if (http_connection->message_header == NO_HEADER) {
							return 400; /* Bad Request. */
						}

						http_connection->nCRLFs = 0;
						http_connection->request_state = 16; /* Multiple lines value. */
					} else if (c == ':') {
						return 400; /* Bad Request. */
					} else if ((c > ' ') && (c < 127)) {
						if ((http_connection->message_header == CONNECTION_HEADER) || (http_connection->message_header == HOST_HEADER) || (http_connection->message_header == KEEP_ALIVE_HEADER) || (http_connection->message_header == PROXY_CONNECTION_HEADER)) {
							http_connection->header_offsets[http_connection->noffsets][1] = offset;
							http_connection->noffsets++;
						}

						/* RFC 822
						 * 3.1.2. STRUCTURE OF HEADER FIELDS
						 * The field-name must be composed of printable ASCII characters
						 * (i.e., characters that have values between 33. and 126.,
						 * decimal, except colon). */
						http_connection->token = offset;

						http_connection->nCRLFs = 0;

						http_connection->request_state = 17; /* Header field. */
					} else {
						return 400; /* Bad Request. */
					}

					break;
				case 16: /* Multiple lines value. */
					if (c == '\r') {
						http_connection->request_state = 14; /* '\r' */
					} else if (c == '\n') {
						http_connection->nCRLFs++;
						http_connection->request_state = 15; /* '\n' */
					} else if (c > ' ') {
						if (http_connection->message_header == RANGE_HEADER) {
							if (http_connection->token) {
								if (buffer_append_char (&http_connection->range_header, ' ') < 0) {
									return -1;
								}
							}
						} else if (http_connection->message_header == USER_AGENT_HEADER) {
							if (http_connection->token) {
								if (buffer_append_char (&http_connection->user_agent, ' ') < 0) {
									return -1;
								}
							}
						} else if ((http_connection->message_header == HOST_HEADER) || (http_connection->message_header == AUTHORIZATION_HEADER) || (http_connection->message_header == IF_MODIFIED_SINCE_HEADER) || (http_connection->message_header == CONTENT_LENGTH_HEADER) || (http_connection->message_header == CONTENT_TYPE)) {
							if (http_connection->token) {
								return 400; /* Bad Request. */
							}
						}

						http_connection->token = offset;
						http_connection->end = 0;

						http_connection->request_state = 19; /* Header value. */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 17: /* Header field. */
					if (c == ':') {
						/* Get header field. */
						http_connection->message_header = get_header (data + http_connection->token, offset - http_connection->token);

						if (http_connection->message_header != UNKNOWN_HEADER) {
							/* If the header was already found... */
							if (http_connection_is_header_present (http_connection, http_connection->message_header)) {
								if ((http_connection->message_header == HOST_HEADER) || (http_connection->message_header == AUTHORIZATION_HEADER) || (http_connection->message_header == CONNECTION_HEADER) || (http_connection->message_header == IF_MODIFIED_SINCE_HEADER) || (http_connection->message_header == CONTENT_LENGTH_HEADER) || (http_connection->message_header == KEEP_ALIVE_HEADER) || (http_connection->message_header == PROXY_CONNECTION_HEADER) || (http_connection->message_header == CONTENT_TYPE)) {
									return 400; /* Bad Request. */
								}

								if (http_connection->message_header == RANGE_HEADER) {
									if (http_connection->range_header.used > 0) {
										if (buffer_append_char (&http_connection->range_header, ',') < 0) {
											return -1;
										}
									}
								} else if (http_connection->message_header == USER_AGENT_HEADER) {
									if (http_connection->user_agent.used > 0) {
										if (buffer_append_char (&http_connection->user_agent, ',') < 0) {
											return -1;
										}
									}
								}
							} else {
								if ((http_connection->message_header == HOST_HEADER) || (http_connection->message_header == CONNECTION_HEADER) || (http_connection->message_header == KEEP_ALIVE_HEADER) || (http_connection->message_header == PROXY_CONNECTION_HEADER)) {
									http_connection->header_offsets[http_connection->noffsets][0] = http_connection->token;
								}

								http_connection_add_header (http_connection, http_connection->message_header);
							}
						}

						http_connection->token = 0;

						http_connection->request_state = 18; /* ':' after header field. */
					} else if (c <= ' ') {
						return 400; /* Bad Request. */
					}

					break;
				case 18: /* ':' after header field. */
					if (c == '\r') {
						http_connection->request_state = 14; /* '\r' */
					} else if (c == '\n') {
						http_connection->nCRLFs++;
						http_connection->request_state = 15; /* '\n' */
					} else if (c > ' ') {
						http_connection->token = offset;
						http_connection->end = 0;

						http_connection->request_state = 19; /* Header value. */
					} else if (!IS_WHITE_SPACE (c)) {
						return 400; /* Bad Request. */
					}

					break;
				case 19: /* Header value. */
					if ((c == '\r') || (c == '\n')) {
						if (http_connection->message_header == HOST_HEADER) {
							/* RFC 2616
							 * 4.2 Message Headers
							 * The field-content does not include any leading or trailing LWS
							 */
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							/* Does the host include the port number? */
							ptr = (char *) memchr (data + http_connection->token, ':', len);
							if (ptr) {
								len = ptr - (data + http_connection->token);

								/* Compute port number. */
								ptr++;
								port = 0;
								while (IS_DIGIT (*ptr)) {
									port = (port * 10) + (*ptr - '0');
									ptr++;
								}

								if (http_connection->host.used > 0) {
									if ((port != http_connection->port) || (http_connection->host.used - 1 != len) || (strncasecmp (http_connection->host.data, data + http_connection->token, len) != 0)) {
										return 400; /* Bad Request. */
									}
								} else {
									if (port != http_server.port) {
										return 404; /* Not Found. */
									}

									if (buffer_append_nul_terminated_string (&http_connection->host, data + http_connection->token, len) < 0) {
										return -1;
									}
								}
							} else {
								if (http_connection->host.used > 0) {
									if ((http_connection->port != 80) || (http_connection->host.used - 1 != len) || (strncasecmp (http_connection->host.data, data + http_connection->token, len) != 0)) {
										return 400; /* Bad Request. */
									}
								} else {
									if (buffer_append_nul_terminated_string (&http_connection->host, data + http_connection->token, len) < 0) {
										return -1;
									}
								}
							}
						} else if (http_connection->message_header == CONNECTION_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							if ((len == sizeof ("Keep-Alive") - 1) && (strncasecmp (data + http_connection->token, "Keep-Alive", len) == 0)) {
								http_connection->keep_alive = 1;
							} else {
								http_connection->keep_alive = 0;
							}
						} else if (http_connection->message_header == AUTHORIZATION_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							ptr = data + http_connection->token;
							if ((len > sizeof ("basic ") - 1) && (strncasecmp (ptr, "basic ", sizeof ("basic ") - 1) == 0)) {
								ptr += (sizeof ("basic ") - 1);
								len -= (sizeof ("basic ") - 1);

								while ((*ptr == ' ') || (*ptr == '\t')) {
									len--;
									ptr++;
								}

								if (buffer_append_size_bounded_string (&http_connection->authorization, ptr, len) < 0) {
									return -1;
								}
							}
						} else if (http_connection->message_header == RANGE_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							if (buffer_append_size_bounded_string (&http_connection->range_header, data + http_connection->token, len) < 0) {
								return -1;
							}
						} else if (http_connection->message_header == USER_AGENT_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							if (buffer_append_size_bounded_string (&http_connection->user_agent, data + http_connection->token, len) < 0) {
								return -1;
							}
						} else if (http_connection->message_header == CONTENT_TYPE) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							if (buffer_append_size_bounded_string (&http_connection->content_type, data + http_connection->token, len) < 0) {
								return -1;
							}
						} else if (http_connection->message_header == IF_MODIFIED_SINCE_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							http_connection->if_modified_since = parsedate (data + http_connection->token, len);
						} else if (http_connection->message_header == CONTENT_LENGTH_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							content_length = 0;

							ptr = data + http_connection->token;
							end = ptr + len;
							while (ptr < end) {
								if ((*ptr < '0') || (*ptr > '9')) {
									return 400; /* Bad Request. */
								}

								content_length = (content_length * 10) + (*ptr - '0');
								ptr++;
							}

							http_connection->content_length = content_length;
						} else if (http_connection->message_header == TRANSFER_ENCODING_HEADER) {
							if (http_connection->end) {
								len = http_connection->end - http_connection->token;
							} else {
								len = offset - http_connection->token;
							}

							if ((len == sizeof ("chunked") - 1) && (strncasecmp (data + http_connection->token, "chunked", len) == 0)) {
								http_connection->chunked = 1;
							}
						}

						if (c == '\r') {
							http_connection->request_state = 14; /* '\r' */
						} else {
							http_connection->nCRLFs++;
							http_connection->request_state = 15; /* '\n' */
						}
					} else if (IS_WHITE_SPACE (c)) {
						if (!http_connection->end) {
							http_connection->end = offset;
						}
					} else if (c > ' ') {
						/* If a new word starts... */
						if (http_connection->end) {
							if ((http_connection->message_header == HOST_HEADER) || (http_connection->message_header == CONTENT_LENGTH_HEADER)) {
								return 400; /* Bad Request. */
							}

							http_connection->end = 0;
						}
					} else {
						return 400; /* Bad Request. */
					}

					break;
			}

			offset++;
		}

		http_connection->offset = offset;
	} while (1);
}

int process_request (http_connection_t *http_connection, host_t **host, const char **mime_type)
{
	buffer_t *path;
	off_t fragment;
	size_t total;
	ssize_t ret;
	rule_t *rule;
	const char *file_extension;

	/* Check version. */
	if (http_connection->major_number != 1) {
		/* HTTP Version Not Supported. */
		return 505;
	} else {
		if (http_connection->minor_number == 1) {
			/* Check host. */
			if (http_connection->host.used == 0) {
				/* Bad Request. */
				return 400;
			}

			*host = host_list_search_host (&http_server.host_list, http_connection->host.data);
		} else if (http_connection->minor_number == 0) {
			/* Check host. */
			if (http_connection->host.used > 0) {
				*host = host_list_search_host (&http_server.host_list, http_connection->host.data);
			} else {
				*host = http_server.host_list.default_host;
			}
		} else {
			/* HTTP Version Not Supported. */
			return 505;
		}
	}

	if (!*host) {
		if ((!http_server.have_proxy) || (!http_connection->proxy_connection)) {
			/* Not Found. */
			return 404;
		}
	} else {
		http_connection->proxy_connection = 0;
	}

	path = &http_connection->path;
	ret = remove_dot_segments ((unsigned char *) path->data, (const unsigned char *) path->data, &http_connection->query, &fragment, &total);
	if (ret == -2) {
		/* Forbidden. */
		return 403;
	} else if (ret == -1) {
		/* Bad Request. */
		return 400;
	}

	path->used = ret + 1;

	if (http_connection->proxy_connection) {
		if (http_connection->query != -1) {
			path->data[http_connection->query] = '?';
			path->used = total + 1;
		} else if (fragment != -1) {
			path->data[fragment] = '#';
			path->used = total + 1;
		}

		return PROXY_CONNECTION_RESPONSE;
	}

	if (((*host)->have_authentication) && ((http_connection->http_method == HEAD_METHOD) || (http_connection->http_method == GET_METHOD))) {
		if (!accesslist_allow (&((*host)->accesslist), path->data, path->used - 1, http_connection->authorization.data, http_connection->authorization.used)) {
			/* Unauthorized. */
			return 401;
		}
	}

	*mime_type = NULL;

	rule = rulelist_match (&((*host)->rulelist), http_connection, &file_extension);
	if ((rule) && (rule->request_handler != LOCAL_REQUEST_HANDLER)) {
		if (http_connection->query != -1) {
			path->data[http_connection->query] = '?';
			path->used = total + 1;
		} else if (fragment != -1) {
			path->data[fragment] = '#';
			path->used = total + 1;
		}

		http_connection->backend_list = &rule->backend_list;

		if (rule->request_handler == FASTCGI_REQUEST_HANDLER) {
			if (http_connection->host.used == 0) {
				if ((buffer_append_size_bounded_string (&http_connection->host, (*host)->host_name, (*host)->host_name_length) < 0) || (buffer_append_char (&http_connection->host, 0)) < 0) {
					return -1;
				}
			}

			return FCGI_CONNECTION_RESPONSE;
		}

		return REVERSE_PROXY_CONNECTION_RESPONSE;
	}

	if ((file_extension) && (file_extension != (const char *) -1)) {
		if ((*mime_type = configuration_get_value (&mime_types, file_extension, NULL)) == NULL) {
			*mime_type = "application/octet-stream";
		}
	}

	if ((http_connection->http_method != GET_METHOD) && (http_connection->http_method != HEAD_METHOD)) {
		/* Not Implemented. */
		return 501;
	}

	return FILE_RESPONSE;
}

int prepare_file_response (http_connection_t *http_connection, host_t *host, const char *mime_type)
{
	static const char http_header[] =
		"Content-Length: %lld\r\n"
		"Connection: %s\r\n"
		"Date: %s, %02d %s %d %02d:%02d:%02d GMT\r\n"
		"Server: %s\r\n"
		"\r\n";

	struct tm *stm;
	struct tm *fmtime;
	buffer_t *buffer;
	buffer_t *parts;
	rangelist_t *rangelist;
	buffer_t *message_body;
	buffer_t *path;
	struct stat buf;
	const char *connection_header;
	char filename[PATH_MAX + 1];
	char boundary[BOUNDARY_SIZE];

#ifdef HAVE_TCP_CORK
	int optval;
#endif /* HAVE_TCP_CORK */

	off_t size;
	off_t parts_size;
	size_t len;
	int error_code;
	size_t nranges;
	size_t i, j;

	len = host->document_root_length;

	path = &http_connection->path;
	if (len + path->used > sizeof (filename)) {
		/* Request-URI Too Long. */
		return 414;
	}

	memcpy (filename, host->document_root, len);
	memcpy (filename + len, path->data, path->used);

	len += (path->used - 1);

	if (stat (filename, &buf) < 0) {
		/* Not Found. */
		return 404;
	}

	buffer = &http_connection->buffer;
	buffer_reset (buffer);

	message_body = &http_connection->message_body;

	fmtime = &http_connection->fmtime;

	/* Directory? */
	if (S_ISDIR (buf.st_mode)) {
		/* If the last slash is missing... */
		if (filename[len - 1] != '/') {
			/* Moved Permanently. */
			return 301;
		}

		size = sizeof (INDEX_PAGE) - 1;
		if (len + size >= sizeof (filename)) {
			/* Request-URI Too Long. */
			return 414;
		}

		memcpy (filename + len, INDEX_PAGE, size + 1);
		if (stat (filename, &buf) < 0) {
			/* If we don't have directory listing... */
			if (!host->dirlisting) {
				/* Not Found. */
				return 404;
			}

			if (directory_listing (host->document_root, path->data, message_body, SORT_BY_NAME, ASCENDING) < 0) {
				/* Internal Server Error. */
				return 500;
			}

			http_connection->response_type = DIRECTORY_LISTING_RESPONSE;

#define DIRLISTING_HEADER "HTTP/1.1 200 OK\r\n"         \
			  "Content-Type: text/html\r\n" \
			  "Cache-Control: no-cache\r\n"
			if (buffer_append_size_bounded_string (buffer, DIRLISTING_HEADER, sizeof (DIRLISTING_HEADER) - 1) < 0) {
				return -1;
			}

			size = message_body->used;
		} else {
			gmtime_r (&buf.st_mtime, fmtime);

			if ((http_connection->if_modified_since != -1) && (buf.st_mtime <= http_connection->if_modified_since)) {
				/* Not Modified. */
				return 304;
			}

			http_connection->response_type = FILE_RESPONSE;

			len += size;

			size = buf.st_size;
			http_connection->filesize = size;

			mime_type = "text/html";
		}
	} else {
		gmtime_r (&buf.st_mtime, fmtime);

		if ((http_connection->if_modified_since != -1) && (buf.st_mtime <= http_connection->if_modified_since)) {
			/* Not Modified. */
			return 304;
		}

		http_connection->response_type = FILE_RESPONSE;

		size = buf.st_size;
		http_connection->filesize = size;
	}

	rangelist = &http_connection->rangelist;
	nranges = 0;
	error_code = 0;

	if (http_connection->response_type == FILE_RESPONSE) {
		if (http_connection->http_method == GET_METHOD) {
			/* Open file. */
			http_connection->fd = open (filename, O_RDONLY);
			if (http_connection->fd < 0) {
				/* Internal Server Error. */
				return 500;
			}
		}

		/* Range header present? */
		if (http_connection->range_header.used > 0) {
			error_code = rangelist_load (rangelist, http_connection->range_header.data, http_connection->range_header.used, size);
			switch (error_code) {
				case -1:
					return -1;
				case 200:
					if (!mime_type) {
						mime_type = get_mime_type (filename, len);
					}

					if (buffer_format (buffer, "HTTP/1.1 200 OK\r\n"
								   "Content-Type: %s\r\n"
								   "Accept-Ranges: bytes\r\n"
								   "Last-Modified: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
								   mime_type,
								   days[fmtime->tm_wday], fmtime->tm_mday, months[fmtime->tm_mon], 1900 + fmtime->tm_year, fmtime->tm_hour, fmtime->tm_min, fmtime->tm_sec) < 0) {
						return -1;
					}

					break;
				case 206:
					nranges = rangelist->used;
					if (nranges > 1) {
						if (!mime_type) {
							mime_type = get_mime_type (filename, len);
						}

						parts = (buffer_t *) malloc ((nranges + 1) * sizeof (buffer_t));
						if (!parts) {
							return -1;
						}

						/* Generate boundary. */
						generate_boundary (boundary, sizeof (boundary));

						parts_size = rangelist_get_bytes (rangelist);

						for (i = 0; i < nranges; i++) {
							buffer_init (&(parts[i]), 128);
							if (buffer_format (&(parts[i]), "\r\n"
											"--%s\r\n"
											"Content-Range: bytes %lld-%lld/%lld\r\n"
											"Content-Type: %s\r\n"
											"\r\n",
											boundary,
											rangelist->ranges[i].from,
											rangelist->ranges[i].to,
											size,
											mime_type) < 0) {
								for (j = 0; j <= i; j++) {
									buffer_free (&(parts[j]));
								}

								free (parts);

								return -1;
							}

							parts_size += parts[i].used;
						}

						buffer_init (&(parts[nranges]), 64);
						if (buffer_format (&(parts[nranges]), "\r\n--%s--\r\n", boundary) < 0) {
							for (i = 0; i <= nranges; i++) {
								buffer_free (&(parts[i]));
							}

							free (parts);

							return -1;
						}

						parts_size += parts[nranges].used;

						http_connection->parts = parts;

						if (buffer_format (buffer, "HTTP/1.1 206 Partial Content\r\n"
									   "Content-Type: multipart/byteranges; boundary=%s\r\n"
									   "Accept-Ranges: bytes\r\n"
									   "Last-Modified: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
									   boundary,
									   days[fmtime->tm_wday], fmtime->tm_mday, months[fmtime->tm_mon], 1900 + fmtime->tm_year, fmtime->tm_hour, fmtime->tm_min, fmtime->tm_sec) < 0) {
							return -1;
						}

						size = parts_size;
					} else {
						if (!mime_type) {
							mime_type = get_mime_type (filename, len);
						}

						if (buffer_format (buffer, "HTTP/1.1 206 Partial Content\r\n"
									   "Content-Type: %s\r\n"
									   "Accept-Ranges: bytes\r\n"
									   "Content-Range: bytes %lld-%lld/%lld\r\n"
									   "Last-Modified: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
									   mime_type,
									   rangelist->ranges[0].from,
									   rangelist->ranges[0].to,
									   size,
									   days[fmtime->tm_wday], fmtime->tm_mday, months[fmtime->tm_mon], 1900 + fmtime->tm_year, fmtime->tm_hour, fmtime->tm_min, fmtime->tm_sec) < 0) {
							return -1;
						}

						size = rangelist_get_bytes (rangelist);
					}

					break;
				case 416:
					/* Requested Range Not Satisfiable. */
					return 416;
			}
		} else {
			error_code = 200;

			if (!mime_type) {
				mime_type = get_mime_type (filename, len);
			}

			if (buffer_format (buffer, "HTTP/1.1 200 OK\r\n"
						   "Content-Type: %s\r\n"
						   "Accept-Ranges: bytes\r\n"
						   "Last-Modified: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
						   mime_type,
						   days[fmtime->tm_wday], fmtime->tm_mday, months[fmtime->tm_mon], 1900 + fmtime->tm_year, fmtime->tm_hour, fmtime->tm_min, fmtime->tm_sec) < 0) {
				return -1;
			}
		}
	}

	if (!http_connection->keep_alive) {
		connection_header = "close";
	} else {
		connection_header = "Keep-Alive";
	}

	stm = &http_server.stm;

	if (buffer_format (buffer, http_header, size, connection_header, days[stm->tm_wday], stm->tm_mday, months[stm->tm_mon], 1900 + stm->tm_year, stm->tm_hour, stm->tm_min, stm->tm_sec, WEBSERVER_NAME) < 0) {
		return -1;
	}

	if (host->log_requests) {
		log_request (&http_server.local_time, http_connection, host);
	}

#ifdef HAVE_TCP_CORK
	if (http_connection->http_method == GET_METHOD) {
		optval = 1;
		setsockopt (http_connection->sd, IPPROTO_TCP, TCP_CORK, &optval, sizeof (int));
	}
#endif /* HAVE_TCP_CORK */

	http_connection->http_connection_state = WRITING_MESSAGE_HEADERS;
	http_connection->offset = 0;

	return 0;
}

void generate_boundary (char *boundary, size_t len)
{
	static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
	size_t i;

	srand (http_server.current_time);

	for (i = 0; i < len - 1; i++) {
		boundary[i] = chars[(int) ((float) (sizeof (chars) - 1) * (rand () / (RAND_MAX + 1.0)))];
	}

	boundary[len - 1] = 0;
}

int do_reverse_proxy_connection (http_connection_t *http_connection, host_t *host)
{
	if (http_server.fn_remove_notifications (http_connection) < 0) {
		return -1;
	}

	/* Connect to backend. */
	if (backend_list_connect ((backend_list_t *) http_connection->backend_list, http_connection) < 0) {
		/* 502 Bad Gateway. */
		return 502;
	}

	if (host->log_requests) {
		http_connection->response_type = FILE_RESPONSE;
		log_request (&http_server.local_time, http_connection, host);
	}

	return 0;
}

int do_proxy_connection (http_connection_t *http_connection)
{
	int sd;
	int index;

	if (http_server.fn_remove_notifications (http_connection) < 0) {
		return -1;
	}

	if (!http_connection->ip) {
		/* Resolve DNS. */
		if (dnscache_lookup (&dnscache, http_connection->host.data, T_A, MAX_HOSTS, &index, http_server.current_time) != DNS_SUCCESS) {
			/* 502 Bad Gateway. */
			return 502;
		}

		/* Connect to HTTP server. */
		if ((sd = connect_to_http_server (&(dnscache.records[index]), http_connection->port)) < 0) {
			/* 502 Bad Gateway. */
			return 502;
		}
	} else {
		/* Connect to HTTP server. */
		if ((sd = connect_to_ip (http_connection->ip, http_connection->port)) < 0) {
			/* 502 Bad Gateway. */
			return 502;
		}
	}

	if (http_server_create_backend_connection (&http_server, sd, http_connection->sd) < 0) {
		close (sd);
		return -1;
	}

	http_connection->fd = sd;

	if (http_server.proxy_log_fd != -1) {
		proxy_log_request (&http_server.local_time, http_connection, &http_connection->host);
	}

	return 0;
}

int do_fcgi_connection (http_connection_t *http_connection, host_t *host)
{
	if (http_server.fn_remove_notifications (http_connection) < 0) {
		return -1;
	}

	/* Connect to FastCGI server. */
	if (backend_list_fcgi_connect ((backend_list_t *) http_connection->backend_list, http_connection) < 0) {
		/* 502 Bad Gateway. */
		return 502;
	}

	if (host->log_requests) {
		http_connection->response_type = FILE_RESPONSE;
		log_request (&http_server.local_time, http_connection, host);
	}

	return 0;
}

int connect_to_http_server (dnscache_entry_t *dnscache_entry, unsigned short port)
{
	rr_t *rr_list;
	struct sockaddr_in addr;
	struct hostent *host;
	int sd;
	int flags;
	size_t i;

	sd = socket (PF_INET, SOCK_STREAM, 0);
	if (sd < 0) {
		if (http_server.log_fd != -1) {
			log_error ("socket: %s.", strerror (errno));
		}

		return -1;
	}

	/* Make socket non-blocking. */
	flags = fcntl (sd, F_GETFL);
	flags |= O_NONBLOCK;
	if (fcntl (sd, F_SETFL, flags) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("fcntl: %s.", strerror (errno));
		}

		close (sd);
		return -1;
	}

	/* Disable the Nagle algorithm. */
	flags = 1;
	setsockopt (sd, IPPROTO_TCP, TCP_NODELAY, (const char *) &flags, sizeof (flags));

	addr.sin_port = htons (port);
	memset (&addr.sin_zero, 0, 8);

	/* Connect. */
	rr_list = dnscache_entry->rr_list;
	for (i = 0; i < dnscache_entry->nhosts; i++) {
		if (rr_list[i].type == T_A) {
			addr.sin_family = AF_INET;
			addr.sin_addr.s_addr = rr_list[i].u.addr;
		} else {
			host = gethostbyname (rr_list[i].u.name.data);
			if (!host) {
				continue;
			}

			addr.sin_family = host->h_addrtype;
			memcpy (&addr.sin_addr, host->h_addr_list[0], host->h_length);
		}

		if ((connect (sd, (struct sockaddr *) &addr, sizeof (struct sockaddr)) < 0) && (errno != EINPROGRESS)) {
			if (http_server.log_fd != -1) {
				log_error ("connect: %s.", strerror (errno));
			}

			/* Try with next host of rr list. */
			continue;
		}

		/* Connected. */
		return sd;
	}

	/* We couldn't connect. */
	close (sd);

	return -1;
}

int connect_to_ip (unsigned int ip, unsigned short port)
{
	struct sockaddr_in addr;
	int sd;
	int flags;

	sd = socket (PF_INET, SOCK_STREAM, 0);
	if (sd < 0) {
		if (http_server.log_fd != -1) {
			log_error ("socket: %s.", strerror (errno));
		}

		return -1;
	}

	/* Make socket non-blocking. */
	flags = fcntl (sd, F_GETFL);
	flags |= O_NONBLOCK;
	if (fcntl (sd, F_SETFL, flags) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("fcntl: %s.", strerror (errno));
		}

		close (sd);
		return -1;
	}

	/* Disable the Nagle algorithm. */
	flags = 1;
	setsockopt (sd, IPPROTO_TCP, TCP_NODELAY, (const char *) &flags, sizeof (flags));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl (ip);
	addr.sin_port = htons (port);
	memset (&addr.sin_zero, 0, 8);

	/* Connect. */
	if ((connect (sd, (struct sockaddr *) &addr, sizeof (struct sockaddr)) < 0) && (errno != EINPROGRESS)) {
		if (http_server.log_fd != -1) {
			log_error ("connect: %s.", strerror (errno));
		}

		close (sd);
		return -1;
	}

	return sd;
}
